home *** CD-ROM | disk | FTP | other *** search
Text File | 1990-10-25 | 22.6 KB | 619 lines | [TEXT/MPS ] |
- {[a-,body+,h-,o=100,r+,rec+,t=4,u+,#+,j=20/57/1$,n+]}
- { UCards.p}
- { Copyright © 1986-1990 by Apple Computer, Inc. All rights reserved. }
- {[f-]}
- {
-
- This is an illustration of one way to implement disk-based documents in MacApp.
-
- Some of the issues arising when using disk-based documents, and some of their
- possible solutions, are:
-
- (1) When starting out with a new document, we need a disk-file behind it
- from the outset (or at least from the first moment that the user
- executes any command). In non-disk-based apps, there is no need to
- obtain a filename and a choice of which disk to write to until the user
- requests a Save, but in the disk-based world we need somewhere to put
- the data from the beginning.
-
- One solution, used by some apps, is to require the user to choose a
- filename and a disk at the time NEW is requested or the app's own icon
- is opened (e.g. Overvue). This starts the user off in what can be a
- rather confusing mode, however.
-
- Another solution is to use a standard filename, always allocating on the
- boot volume or, perhaps on the default volume, for the new file (e.g.
- disk-based MacWrite, which uses a file named 'NewFile'). This solution
- may require that the app only support one document open at a time.
- At application shut-down time, if not before, this file can be purged.
-
- A third, better, solution would be to choose an initial filename more
- intelligently -- say make its name be a function of the application's
- name and the date and time, perhaps, thus resolving name conflicts and
- also giving the user more information if for some reason he should find
- that file on his disk (such as after a power failure or application
- crash).
-
-
- (2) When an existing file is opened and then the document is edited, if the
- app wishes to support 'Revert' then it must preserve information both
- about the previously-saved state and about the changes made. This adds
- complexity to the design.
-
- One solution is to retain two copies of the document file, one of which
- has the most recently saved version, and the other the current
- working-version. Thus, one always has something to revert to. When
- reopening an old document, the app starts out by making a complete,
- exact, copy of it in a new file (attaching the original filename to one
- or the other and a temporary filename to whichever one doesn't retain
- the original filename). All edits are then made on the copy. Each
- time there is a Save, the current working version becomes the 'saved'
- version, and a copy of it becomes the current working version (or
- something equivalent to that).
-
- This solution has the problem that during editing of a document, twice
- as much disk is consumed as necessary. But it leads to cleanliness of
- design.
-
- Another solution is to retain in a single disk file all the information
- about the most-recently saved version AND all the changes since then.
- This requires a more complex file format.
-
- A third solution is to use the latest Save-file as a read-only data base,
- with all changes posted to a second, read-write 'Changes' file. In this
- scheme, each "Save" is carried out by merging the edits logged in the
- Changes file with the data in the latest Save file, thus producing a
- new Save-file, at which time the Changes file is reset to empty. This
- solution is clean and comprehensive, but requires quite a bit more code
- and design, since the application must understand two different file
- formats, must be able to merge save-data and changes-data dynamically
- so that the appearance on the screen is right, etc.
-
- A fourth solution is to abandon the 'Revert to Previous Version' feature.
- In this case, the actual Save-file can be opened for read-write, left
- open throughout the time the document is open, and it can be updated in
- place.
-
-
- In this simple example, easier alternatives were chosen at most junctures.
- Thus:
-
- -- An open document has two disk files, which we will call the Working File
- and the Saved File; but an open document that has never been saved
- does not have a Saved File yet.
- -- The 'Saved File' has the most recently saved version, and resides in a
- file whose name is the same as the one the user thinks of.
- -- The 'Working File' is a file with a generated name on the default volume
- (if the default volume can't be written to, or doesn't have enough
- space to hold this file, an error is issued, and editing cannot
- proceed -- this is the same as in MacWrite).
- -- Each time there is a Save, the following process is followed: A new
- saved file is created by copying over everything from the Working
- file and existing Saved file.
- -- Revert is implemented by clearing the Working File.
- -- For "Save As", the old Saved File is abandoned in whatever state it was
- in, and a new Saved File is created as for Save.
-
-
- C A C H I N G:
-
- In a disk-based document, some of the data are available in memory and
- others must be read from disk. To avoid having to go to disk too often,
- it is expedient to implement a caching scheme, whereby the last n data
- objects (paragraphs, file records, notecards, what have you) are kept
- in memory.
-
- An easy caching scheme is offered here.
-
- The model followed is that ALL cards belonging to a document are
- representing by OBJECTS in memory (a List of elements of type TCard).
- The information stored in the TCard object itself is minimal. The full
- information about any Card is stored on Disk, and is brought into the
- cache in memory only when required (typically when it is displayed as
- the current card).
-
- The Cache is represented by another List, whose elements are also of
- type TCard. It is maintained on a First-in First-out basis, so that
- the most recently accessed n cards are in the cache.
-
- Typically, a TCard item will have a handle to its lengthier data, which
- will be NIL (or perhaps some negative value, to store retrieval
- information or something else useful) when the Card is not in the cache,
- but will be a true handle to data when it is in the cache.
-
- }
- {[f+]}
-
- UNIT UCards;
-
- INTERFACE
-
- USES
-
- { • MacApp }
- UMacApp,
-
- { • Building Blocks }
- UPrinting, UTEView,
-
- { • Implementation use }
- Packages, ToolUtils;
-
- CONST
-
- cNextCard = 1000; { Next Card command }
- cPrevCard = 1001; { Previous Card command }
- cFirstCard = 1002; { First Card command }
- cLastCard = 1003; { Last Card command }
- cNewCard = 1010; { New Card command }
- cDeleteCard = 1011; { Delete Card command }
- kSignature = 'SS10'; { Application signature }
- kFileType = 'SF10'; { File-type code used for document files
- created by this application }
-
- kWindowRsrcID = 1000; { Id of the window view template }
- kCardViewRsrcID = 1001; { Id of the card view template }
-
- kWorkType = 'SW10'; { File type for the work file }
-
- TYPE
-
- CardDocData = { This is the data stored at the beginning
- of the disk file }
- RECORD
- theCardCount: INTEGER; { number of cards in the document }
- theShownCard: INTEGER; { Card currently being shown, or -1 }
- { ??? add window-state info, etc., here }
- END;
-
- TCardsApplication = OBJECT (TApplication)
-
- PROCEDURE TCardsApplication.ICardsApplication;
- { Initialize the application }
-
- FUNCTION TCardsApplication.DoMakeDocument(itsCmdNumber: CmdNumber): TDocument;
- OVERRIDE;
- { Launches a TCardsDocument; called when application's icon is opened,
- and when New or Open is requested by the user }
-
- PROCEDURE TCardsApplication.DoShowAboutApp; OVERRIDE;
- { Overridden in order to give a current-status report when "About <appName>"
- is selected from the apple menu }
-
- END;
-
- {$IFC qHasForward}
- TCardView = OBJECT; FORWARD;
- TEmptyView = OBJECT; FORWARD;
- TCardCache = OBJECT; FORWARD;
- TCard = OBJECT; FORWARD;
- {$EndC}
-
- TCardDocument = OBJECT (TDocument)
-
- fCardView: TCardView; { The view that shows the cards }
- fEmptyView: TEmptyView; { The view that says there are no cards }
- fCurrView: TView; { Which view are we looking at? }
-
- fCards: TList; { a list containing a TCard object for each
- card in the data base; it serves as a
- dictionary for the data base }
-
- fCache: TCardCache; { a list of cards cached in memory; all
- others have their data available only by
- going to disk }
-
- fCardDocData: CardDocData; { Info about the document's contents }
-
- fReopening: BOOLEAN; { Used when we restore window state (not
- done now) }
-
- fWorkFileName: Str255; { Name of the current work file }
- fWorkVRefNum: INTEGER; { Volume refNum of the current work file }
- fWorkRefNum: INTEGER; { Refnum of the current work file }
-
- fWorkNext: LONGINT; { File-location for next new card to be
- added to the work file }
-
- fFirstWritten: LONGINT; { File-location of first card written during
- last DoWrite }
-
- { Initialization and termination }
-
- PROCEDURE TCardDocument.ICardDocument;
- { Initialize the document }
-
- PROCEDURE TCardDocument.Free; OVERRIDE;
- { Free the document }
-
- { TDocument methods for opening and closing }
-
- PROCEDURE TCardDocument.FreeData; OVERRIDE;
- { Deletes the work file and in-memory structures }
-
- PROCEDURE TCardDocument.DoInitialState; OVERRIDE;
- { Set up empty document }
-
- PROCEDURE TCardDocument.DoMakeViews(forPrinting: BOOLEAN); OVERRIDE;
- { Launches the views which are seen in the document's window }
-
- PROCEDURE TCardDocument.ShowReverted; OVERRIDE;
- { Set the views properly when reverting }
-
- PROCEDURE TCardDocument.SwapViews(fromView, toView: TView);
- { Swap current view }
-
- { Overrides of TDocument methods for Filing }
-
- PROCEDURE TCardDocument.DoNeedDiskSpace(VAR dataForkBytes,
- rsrcForkBytes: LONGINT); OVERRIDE;
- { Tells how many bytes of disk space will be required to store the
- data for the document in a file on disk }
-
- PROCEDURE TCardDocument.DoRead(aRefNum: INTEGER; rsrcExists,
- forPrinting: BOOLEAN); OVERRIDE;
- { Actually reads in the data from the disk, when a document is
- Opened or Reverted }
-
- PROCEDURE TCardDocument.DoWrite(aRefNum: INTEGER; makingCopy: BOOLEAN); OVERRIDE;
- { Actually writes the data to the disk, when a document is Saved }
-
- { Work-file management }
-
- PROCEDURE TCardDocument.ConstructCardIndex(aRefNum: INTEGER);
- { Build the in-memory index to the card document }
-
- PROCEDURE TCardDocument.CreateWorkFile;
- { Creates the Work file }
-
- PROCEDURE TCardDocument.EmptyWorkFile;
- { Resets the work file to be empty }
-
- PROCEDURE TCardDocument.PurgeWorkFile;
- { Purges the Work file }
-
- { Menu management }
-
- PROCEDURE TCardDocument.DoSetupMenus; OVERRIDE;
- { Enable menu commands }
-
- FUNCTION TCardDocument.DoMenuCommand(aCmdNumber: CmdNumber): TCommand; OVERRIDE;
- { Handle menu commands }
-
- { Card management }
-
- FUNCTION TCardDocument.AddCard: INTEGER;
- { Add a card to the document's data set }
-
- PROCEDURE TCardDocument.DeleteCard(aCard: TCard);
- { Called when a card is to be deleted from the file }
-
- PROCEDURE TCardDocument.CacheCard(aCard: TCard);
- { If the card is not currently in the cache, read it from disk,
- first making space in the cache if neceessary; if it already
- IS in the cache, move it to the front of the cache, so that it
- will be the last to be pushed out }
-
- PROCEDURE TCardDocument.ReadCardFromDisk(aCard: TCard);
- { Reads the card into the work file }
-
- FUNCTION TCardDocument.FirstCard: INTEGER;
- { Return the first non-deleted card in the file }
-
- FUNCTION TCardDocument.LastCard: INTEGER;
- { Return the last non-deleted card in the file }
-
- FUNCTION TCardDocument.NextCard(theCard: INTEGER): INTEGER;
- { Return the next non-deleted card after the given card }
-
- FUNCTION TCardDocument.PrevCard(theCard: INTEGER): INTEGER;
- { Return the previous non-delete card before the given card }
-
- PROCEDURE TCardDocument.PurgeCards;
- { Delete all card objects associated with the document }
-
- PROCEDURE TCardDocument.SavedOn(VAR fileName: Str255; volRefNum: INTEGER); OVERRIDE;
- { Note that we are using a new disk file }
-
- PROCEDURE TCardDocument.WriteCardToDisk(aCard: TCard);
- { Writes the card out to the work file }
-
- { Inspecting }
- PROCEDURE TCardDocument.Fields(PROCEDURE
- DoToField(fieldName: Str255; fieldAddr: Ptr;
- fieldType: INTEGER)); OVERRIDE;
-
- END;
-
- {$IFC qHasForward}
- TCardEditCommand = OBJECT; FORWARD;
- {$EndC}
-
- TCardView = OBJECT (TTEView)
- { TCardView is the view used to show a card in the card window }
-
- fCardDocument: TCardDocument; { Card document shown in view }
- { ??? In retrospect, the next two fields really should belong to
- TCardDocument, as they represent document state and we have to do
- a lot of gymnastics now to keep TCardView in synch with TCardDocument. }
- fCurrCard: TCard; { The currently shown card }
- fCurrNumber: INTEGER; { Index of card being shown }
- fCardEditCommand: TCardEditCommand; { Current editing command, if any }
-
- { Creation/Destruction methods }
-
- PROCEDURE TCardView.IRes(itsDocument: TDocument; itsSuperView: TView;
- VAR itsParams: Ptr); OVERRIDE;
- { Create the view from a resource template}
-
- PROCEDURE TCardView.Free; OVERRIDE;
- { Free our data before disposing }
-
- { View business }
-
- PROCEDURE TCardView.InstallText(newText: Handle);
- { Change the contents of this view to the specified text }
-
- PROCEDURE TCardView.ShowReverted; OVERRIDE;
- { Adjust the view after a revert }
-
- { Card handling }
-
- PROCEDURE TCardView.InstallCard(theCard: INTEGER);
- { Make the specified card the current card }
-
- PROCEDURE TCardView.UpdateCard(theCard: TCard);
- { theCard is no longer the current card; update its text if necessary }
-
- { Commands and Menus }
-
- FUNCTION TCardView.DoKeyCommand(ch: Char; aKeyCode: INTEGER;
- VAR info: EventInfo): TCommand; OVERRIDE;
- { Capture command coming back from TEView.DoKeyCommand }
-
- FUNCTION TCardView.DoMenuCommand(aCmdNumber: CmdNumber): TCommand; OVERRIDE;
- { Handle menu commands, including those for TEView }
-
- PROCEDURE TCardView.DoSetupMenus; OVERRIDE;
- { Enable the appropriate menu choices }
-
- { Inspecting }
- PROCEDURE TCardView.Fields(PROCEDURE
- DoToField(fieldName: Str255; fieldAddr: Ptr;
- fieldType: INTEGER)); OVERRIDE;
-
- END;
-
-
- TEmptyView = OBJECT (TView)
- { TEmptyView is used to show a message when there are no cards in the
- database. It could have been part of TCardView, but swapping views is
- easier than overriding all the methods of TTEView which would have to
- do nothing if there were no card showing. }
-
- PROCEDURE TEmptyView.Resize(width, height: VCoordinate;
- invalidate: BOOLEAN); OVERRIDE;
- { Force a redraw on resize since the text is filled to the box }
- PROCEDURE TEmptyView.Draw(area: Rect); OVERRIDE;
- { Draw the view saying there are no cards }
-
- END;
-
- TCacheableObject = OBJECT (TObject)
- fGeneration: INTEGER; { How long I've been lying around in memory
- }
- fLocked: BOOLEAN; { Object cannot be purged from the cache }
-
- PROCEDURE TCacheableObject.ICacheableObject;
- { Initialize the cacheableobject }
-
- PROCEDURE TCacheableObject.Fields(PROCEDURE
- DoToField(fieldName: Str255; fieldAddr: Ptr;
- fieldType: INTEGER)); OVERRIDE;
-
- END;
-
- TCard = OBJECT (TCacheableObject)
- { There is one TCard object for every card in the database. }
-
- fData: Handle; { Handle to the data on the card; if NIL,
- card is not in memory }
- fDataSize: INTEGER; { Number of bytes of data on the card (same
- as GetHandleSize(fData) when fData is in
- memory) }
- fDirty: BOOLEAN; { Whether the data on the card has been
- changed since it was last read in from
- disk }
- fChanged: BOOLEAN; { Whether the card currently resides in work
- file }
- fDeleted: BOOLEAN; { Whether the card has been deleted from the
- file }
-
- fLocInFile: LONGINT; { Location of the card's data in the
- database file }
-
- fCardDocument: TCardDocument; { The document I belong to }
-
- { Initialization, destruction }
-
- PROCEDURE TCard.ICard(itsFileLocation: LONGINT; itsDocument: TCardDocument);
- { Initialize the card }
-
- PROCEDURE TCard.Free; OVERRIDE;
- { Hose the card }
-
- { Modification }
-
- PROCEDURE TCard.Changed(itsFileLocation: LONGINT);
- { This card should be sent to the work file }
-
- PROCEDURE TCard.NewText(aHandle: Handle);
- { The card has some new text }
-
- { Filing }
-
- PROCEDURE TCard.NewHome(itsFileLocation: LONGINT);
- { Note that the card has a new place in the document file }
-
- PROCEDURE TCard.ReadFrom(aRefNum: INTEGER);
- { Read the card's data from disk }
-
- PROCEDURE TCard.WriteTo(aRefNum: INTEGER);
- { Write the card's data to disk }
-
- PROCEDURE TCard.WriteCopy(aRefNum: INTEGER);
- { Write the card's data to some file }
-
- { Inspecting }
- PROCEDURE TCard.Fields(PROCEDURE DoToField(fieldName: Str255; fieldAddr: Ptr;
- fieldType: INTEGER)); OVERRIDE;
-
- END;
-
- TCardCache = OBJECT (TList)
- { Again a card cache really is too specific of an object because it has knowledge
- from the programmer about what types of objects it is going to contain (namely cards).
- If we wanted to generalize, functions such as cachecard, cardisincache, etc. would
- take an additional argument which would be a predicate function (that is, a function
- that returns true or false) to some question. For example, CardIsInCache might
- become:
- TCache.IsInCache(PROCEDURE TEST(anObject: TObject); anObject: TObject); }
-
- fMaxSize: INTEGER; { How large can I grow }
- fGrowthRate: INTEGER; { How fast to grow after I hit my limit and
- no cards are purgable }
- fGeneration: INTEGER; { Generation of last object in the cache }
-
- PROCEDURE TCardCache.ICardCache(size, growth: INTEGER);
-
- PROCEDURE TCardCache.CacheCard(aCardDocument: TCardDocument; aCard: TCard);
-
- FUNCTION TCardCache.CardIsInCache(aCard: TCard): BOOLEAN;
-
- PROCEDURE TCardCache.Delete(item: TObject); OVERRIDE;
-
- PROCEDURE TCardCache.FreeDocCards(aCardDocument: TCardDocument);
-
- PROCEDURE TCardCache.Insert(item: TObject); OVERRIDE;
-
- PROCEDURE TCardCache.Grow;
-
- FUNCTION TCardCache.PurgeableCard(aCardDocument: TCardDocument): TCard;
-
- PROCEDURE TCardCache.Touch(aCard: TCard);
-
- PROCEDURE TCardCache.Fields(PROCEDURE
- DoToField(fieldName: Str255; fieldAddr: Ptr;
- fieldType: INTEGER)); OVERRIDE;
-
- END;
-
- TCardEditCommand = OBJECT (TCommand)
- { TCardEditCommand objects are used to "encapsulate" TCommand when a
- card is being edited (either via the Edit menu or typing). A cleaner
- mechanism is provided by the TTEView methods DoMakeEditCommand and
- DoMakeTypingCommand, which can be overridden to return a descendant
- command object instead. But in this case, TCardEditCommand doesn't
- care whether it's dealing with a TTETypingCommand or an Edit menu
- command, so rather than declare one descendant of TTECommand and one
- descendant of TTETypingCommand, which both do the same thing, we use
- the TTECommand as a subsidiary object to our own command. }
-
- fEncapsulatedCommand: TCommand; { encapsulated TCommand }
- fCardView: TCardView; { associated CardView }
- fCardNumber: INTEGER; { number of card being edited }
- fCard: TCard; { card itself }
- fReserve: Handle; { Use to "reserve" memory--it is used to
- ensure there is enough memory to copy
- the TEView's text to the card when
- Commit calls UpdateCard. }
-
- PROCEDURE TCardEditCommand.ICardEditCommand(itsCmdNumber: CmdNumber;
- itsView: TCardView;
- itsEncapsulatedCommand: TCommand);
-
- PROCEDURE TCardEditCommand.Free; OVERRIDE;
- { Clean up }
-
- PROCEDURE TCardEditCommand.DoIt; OVERRIDE;
- { Perform the edit of the card }
-
- PROCEDURE TCardEditCommand.UndoIt; OVERRIDE;
- { Undo the edit to the card }
-
- PROCEDURE TCardEditCommand.RedoIt; OVERRIDE;
- { Redo the edit to the card }
-
- PROCEDURE TCardEditCommand.Commit; OVERRIDE;
- { Commit the edit to the card }
-
- PROCEDURE TCardEditCommand.Fields(PROCEDURE
- DoToField(fieldName: Str255; fieldAddr: Ptr;
- fieldType: INTEGER)); OVERRIDE;
-
- END;
-
- TNewCardCommand = OBJECT (TCommand)
- { This command is used to create a new card. }
-
- fCardDocument: TCardDocument; { associated card document }
- fCardNumber: INTEGER; { number of card being added }
- fCard: TCard; { card itself }
- fSavedSelection: INTEGER; { Saved number of currently selected card }
-
- PROCEDURE TNewCardCommand.INewCardCommand(itsCmdNumber: CmdNumber;
- itsCardDocument: TCardDocument);
-
- PROCEDURE TNewCardCommand.DoIt; OVERRIDE;
- { Perform the addition of the card }
-
- PROCEDURE TNewCardCommand.UndoIt; OVERRIDE;
- { Undo the addition of the card }
-
- PROCEDURE TNewCardCommand.RedoIt; OVERRIDE;
- { Redo the addition of the card }
-
- PROCEDURE TNewCardCommand.Commit; OVERRIDE;
- { Commit the addition of the card }
-
- PROCEDURE TNewCardCommand.Fields(PROCEDURE
- DoToField(fieldName: Str255; fieldAddr: Ptr;
- fieldType: INTEGER)); OVERRIDE;
-
- END;
-
- TDeleteCardCommand = OBJECT (TCommand)
- { This command is used to delete an existing card. }
-
- fCardDocument: TCardDocument; { associated card document }
- fCardNumber: INTEGER; { number of card being added }
- fCard: TCard; { card itself }
-
- PROCEDURE TDeleteCardCommand.IDeleteCardCommand(itsCmdNumber: CmdNumber;
- itsCardDocument: TCardDocument;
- itsCard: TCard; itsNumber: INTEGER);
-
- PROCEDURE TDeleteCardCommand.DoIt; OVERRIDE;
- { Perform the deletion of the card }
-
- PROCEDURE TDeleteCardCommand.UndoIt; OVERRIDE;
- { Undo the deletion of the card }
-
- PROCEDURE TDeleteCardCommand.RedoIt; OVERRIDE;
- { Redo the deletion of the card }
-
- PROCEDURE TDeleteCardCommand.Commit; OVERRIDE;
- { Commit the deletion of the card }
-
- PROCEDURE TDeleteCardCommand.Fields(PROCEDURE
- DoToField(fieldName: Str255; fieldAddr: Ptr;
- fieldType: INTEGER)); OVERRIDE;
-
- END;
-
- IMPLEMENTATION
-
- {$I UCards.inc1.p}
-
- END.
-